Source code for hysop.tools.decorators

# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import functools
import inspect
import types
import warnings
import sys
import traceback
from functools import wraps as __wraps
from abc import ABCMeta

from hysop.constants import __DEBUG__, __VERBOSE__, __PROFILE__, HYSOP_ROOT
from hysop.tools.sys_utils import SysUtils
from hysop.tools.warning import HysopDeprecationWarning


[docs] def wraps(f): """ Like functools.wraps but keep trace of the original wrapped function in the '__wrapped__' attribute. This is usefull to trace functions calls origin. See hysop.tools.decorators.debug """ if __DEBUG__: if not hasattr(f, "__wrapped__"): setattr(f, "__wrapped__", f) wrapper_assignments = functools.WRAPPER_ASSIGNMENTS + ("__wrapped__",) g = __wraps(f, wrapper_assignments) else: g = __wraps(f) return g
[docs] def static_vars(**kwargs): """ Attach a static variables local to decorated function. """ def decorate(f): for k in kwargs: setattr(f, k, kwargs[k]) return f return decorate
[docs] @static_vars(call_depth=-1) def debug(f): """ Debug decorator Usage: @debug before function definition you want to debug If verbose is set to True: prints filename and line of function definition Else: only print depth of call and function name """ verbose = False # __VERBOSE__ if __DEBUG__: @wraps(f) def decorator(*args, **kw): debug.call_depth += 1 cls = None fw = f if isinstance(f, types.FunctionType) and hasattr(args[0], "__class__"): cls = args[0] if isinstance(args[0], type) else type(args[0]) if hasattr(f, "__wrapped__"): fw = f.__wrapped__ for _cls in inspect.getmro(cls)[::-1]: if _cls is ABCMeta: continue if hasattr(_cls, fw.__name__): _f = getattr(_cls, fw.__name__) if hasattr(_f, "__wrapped__"): _f = _f.__wrapped__ if _f is fw: cls = _cls break cls = cls.__name__ if hasattr(args[0], "name"): cls += f"({args[0].name})" print( "{}{}{}{}{}()".format( ( "{}:".format( fw.__code__.co_filename.replace(HYSOP_ROOT, "hysop") ) if verbose else "" ), f"{fw.__code__.co_firstlineno} " if verbose else "", ">" * debug.call_depth if not verbose else "", f"{cls}::" if (cls is not None) else "", fw.__name__, ) ) if "name" in kw: print("with name {}.".format(kw["name"])) if f.__name__ == "__new__": fullclassname = args[0].__mro__[0].__module__ + "." fullclassname += args[0].__mro__[0].__name__ print(f"=> {fullclassname}") print() # Calling f ret = f(*args, **kw) # try: # ret = f(*args, **kw) # except Exception as e: # fn='{}{}{}{}()'.format( # '{}:'.format(f.__code__.co_filename.replace(HYSOP_ROOT, 'hysop')), # '{}'.format(f.__code__.co_firstlineno), # '::{}.'.format(cls) if (cls is not None) else '::', # f.__name__) # if not hasattr(e, 'debug_error'): # msg = '\nFATAL ERROR: Failed to call {}:'.format(fn) # print(msg) # print('got exception:') # exc_type, exc_value, exc_traceback = sys.exc_info() # traceback.print_exception(exc_type, exc_value, exc_traceback, limit=3) # print # print('DEBUG CALLSTACK IS:') # msg = ' >{} with len(*args)={} and **kwds={}.' # msg=msg.format(fn, len(args), kw.keys()) # print(msg) # e.debug_error = True # raise e debug.call_depth -= 1 if debug.call_depth == -1: print return ret return decorator else: # define empty debug decorator: return f
[docs] def profile(f): """ Decorator to enable function profiling of a method inside a class. The concerned class must have a Profiler attribute. """ if __PROFILE__: from hysop.core.mpi import Wtime @wraps(f) def _profile(*args, **kwargs): """args[0] contains the object""" t0 = Wtime() res = f(*args, **kwargs) args[0]._profiler[f.__name__] += Wtime() - t0 return res return _profile else: return f
[docs] def deprecated(f): @wraps(f) def func(*args, **kwargs): cls = None if isinstance(f, types.FunctionType): if hasattr(args[0], "__class__"): cls = args[0].__class__.__name__ elif hasattr(args[0], "__name__"): cls = args[0].__name__ msg = ( "Use of deprecated function {}{}{}{}()".format( "{}:".format(f.__code__.co_filename.replace(HYSOP_ROOT, "hysop")), f"{f.__code__.co_firstlineno}", f"::{cls}." if (cls is not None) else "::", f.__name__, ), ) warnings.warn(msg, HysopDeprecationWarning) return f(*args, **kwargs) return func
[docs] def requires_cmd(*args): """ Raise a RuntimeError if given executable names are not found in system PATH. """ def decorate(func): for cmd in args: if not SysUtils.cmd_exists(cmd): msg = "Function '{}' requires executable '{}' to be present " msg += "on your system but it was not found." msg = msg.format(func.__name__, cmd) raise RuntimeError(msg) return func return decorate
[docs] def required_property(f): @wraps(f) def decorator(*args, **kargs): msg = "Property corresponding to getter {}() has not been correctly set up in class {}." msg = msg.format(f.__name__, args[0].__class__) raise RuntimeError(msg) return decorator
[docs] def optional_property(f): @wraps(f) def decorator(*args, **kargs): msg = ( "Property corresponding to getter {}() is optional and " + "has not been set up in class {}." ) msg = msg.format(f.__name__, args[0].__class__) raise RuntimeError(msg) return decorator